package com.cm.tianwt.socks.service;

import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.UnknownHostException;
import java.util.Map;

import javax.net.SocketFactory;

import com.cm.tianwt.socks.utils.Configs;
import com.cm.tianwt.socks.utils.LogUtils;
import com.cm.tianwt.socks.utils.NetworkUtils;
import com.cm.tianwt.socks.utils.UserManager;
import com.cm.tianwt.socks.utils.Utils;

public class SocksV5Impl extends SocksHandler {
	
	private static final int METHOD_AUTHUSER = 0x02;
	private static final int METHOD_ALLOWALL = 0x00;
	private Socket server;
	private InputStream mClientInputStream;
	private OutputStream mClientOutputStream;
	
	private int[] CLIENT_SUPPORT_METHODS = null;
	
	
	public final int[] AUTH_METHODS = {
			0x00,//NO AUTHENTICATION REQUIRED
			0x01,//GSSAPI
			0x02,//USERNAME/PASSWORD
			0x03,//to 至 X'7F' IANA 分配(IANA ASSIGNED) ,IANA是负责全球Internet上的IP地址进行编号分配的机构。
			0x07,
			0x80,//to  至 X'FE' 私人方法保留(RESERVED FOR PRIVATE METHODS) 
			0x09, //NO ACCEPTABLE METHODS
			0xff,
			
	};
	
	public final byte[] RESPONSE_CODES = {
			
			0x00,// succeeded
			0x01,// general SOCKS server failure
			0x02,// connection not allowed by ruleset
			0x03,// Network unreachable
			0x04,// Host unreachable
			0x05,// Connection refused
			0x06,// TTL expired
			0x07,// Command not supported
			(byte) 0x08,// Address type not supported
			(byte) 0xff,// X'09 to X’FF’ unassigned
	};
	
	
	public final byte[] AUTH_USER_PASSWORD = {0x05,0x02};
	public final byte[] AUTH_NONE = {0x05,0x00};
	
	
	public final byte[] AUTH_STATE_FAILED = {0x05,0x01};
	public final byte[] AUTH_STATE_SUCCESS = {0x05,0x00};
	
	
	private InetAddress mServerAddress;
	private int  mServerPort;
	
	private int UNUSED;
	private int command;
	private int addressType;
	
	private InetAddress mClientAddress;
	private int mClientPort;
	
	private byte[] mDstAddress;
	private byte[] mDstPort;
	private InputStream mServerInputStream;
	private OutputStream mServerOutputStream;
	private ServerSocket mBindTempServer;
	private DatagramSocket mDGSocket;
	private byte[] mUdpBuffer;
	private DatagramPacket mDGPack;
	private String UID;
	private int mUdpClientPort;
	private int UDP_port;
	private InetAddress UDP_IA;

	public SocksV5Impl(Socket clientSocket, InputStream is, OutputStream os) {
		super(clientSocket);
		this.mClientInputStream = is;
		this.mClientOutputStream = os;
		
	}

	@Override
	public void startService() {
		SocksHandlerManager.execute(this);
	}

	@Override
	public void run() {
		
		try {
			readHeader();
			boolean granted = checkAuthentication();
			if(!granted) {
				throw new IllegalAccessException("Auth user is failed!");
			}
			assertProxyInfo();
			
			switch (this.command) {
				case Configs.COMMAND_CONNECT:
					startConnect();
					if(isReady()) {
						 transport();
					}else {
						 refuseAction(RESPONSE_CODES[0x01]);
					}
					
					break;
				case Configs.COMMAND_BIND:
					startBind();
					if(isReady()) {
						transport();
					}else {
						refuseAction(RESPONSE_CODES[0x01]);
					}
					break;
				
				case Configs.COMMAND_UDP:
					startUdp();
					break;
				default:
					break;
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			shutDown();
		}
		
	}

	private void transport() throws Exception {
		
		 // 判断IO是否已经结束
		boolean isActive = true;
		while( isActive )	{
			synchronized (this.UID.intern()) {
				try {
					boolean isEOF = sendDataToSever();
					if(isEOF) {
						// 判断IO是否已经结束
						break;
					}
					sendDataToClient();
					
				} catch (Exception e) {
					e.printStackTrace();
					isActive = false;
				}
			}
			Thread.yield();
			
		}	
	}

	private boolean sendDataToSever() throws Exception {
		boolean isEOF = true;
		byte[] buffer = new byte[Configs.BUFFER_DEFAULT_SIZE];
		while(true) {
			int len = -1;
			try {
				if(client!=null && client.isConnected() &&!client.isInputShutdown()){
					len = mClientInputStream.read(buffer, 0, buffer.length);
				 }
			}catch (InterruptedIOException e) {
				len = 0;
			}
			catch(Exception e) {
				isEOF = true;
				shutDown();
			}
			if(len>0) {
				logClientData(len);
				isEOF = false;
				mServerOutputStream.write(buffer, 0, len);
				mServerOutputStream.flush();
				continue;
			}
			break;
		}

		return isEOF;
	}

	private void sendDataToClient() throws Exception {
		byte[] buffer = new byte[Configs.BUFFER_DEFAULT_SIZE];
		while(true) {
			int len = -1;
			try {
				if(server!=null && server.isConnected() && !server.isInputShutdown()){
					len = mServerInputStream.read(buffer, 0, Configs.BUFFER_DEFAULT_SIZE);
				}
			}catch(InterruptedIOException e) {
			}catch (Exception e) {
				shutDown();
			}
			if(len>0) {
				logServerData(len);
				mClientOutputStream.write(buffer, 0, len);
				mClientOutputStream.flush();
				continue;
			}
			break;
		}
	}

	private void assertProxyInfo() throws IOException {
		
			int version = readClientByte();
			if(!Utils.checkVersion(version,Configs.VERSION_SOCKS_V5)) {
				refuseAction( RESPONSE_CODES[0x02]);
				throw new IOException("Can't support to change socks version");
			}
			command = readClientByte();
			
			if(command<Configs.COMMAND_CONNECT || command>Configs.COMMAND_UDP ) {
				refuseAction( RESPONSE_CODES[0x07]);
				throw new IOException("Can't support the command="+command);
			}
			
			UNUSED = readClientByte();
	
			addressType = readClientByte();
			
			if(addressType!=Configs.ADDRESS_DOMAIN && addressType!=Configs.ADDRESS_IPV4 && addressType!=Configs.ADDRESS_IPV6) {
				refuseAction( RESPONSE_CODES[0x08]);
				throw new IOException("Can't support the command="+command);
			}
			
			
			mServerAddress = readServerAddress(addressType);
			mServerPort = readServerPort();
			
			if(mServerAddress==null ) {
				refuseAction( RESPONSE_CODES[0x04]);
				throw new IOException("Can't resovle server address");
			}
	
			mClientAddress = client.getInetAddress();
			mClientPort = client.getPort();
			
			if(!NetworkUtils.checkIsReachable(mServerAddress,mServerPort)) {
				refuseAction( RESPONSE_CODES[0x06]);
				throw new IOException("Can't connect server address");
			}

			this.UID = UserManager.getInstance().createToken(mClientAddress,mClientPort,command);
		
	}

	private void refuseAction(int code) throws IOException {
		byte[] data = getCheckReplyResult(code);
		sendToClient(data);
	}

	private byte[] getCheckReplyResult(int code) {  
		/**
		 * 代理服务器评估阶段使用的DST_ADDR和DST_PORT作为响应命令
		 */
		byte[] heads = new byte[] {0x05,(byte) code,0x00,(byte) addressType};
		byte[] data = null;
		
		if(addressType==Configs.ADDRESS_DOMAIN) {
			  data = Utils.combineArray(heads,new byte[] {(byte) this.mDstAddress.length},this.mDstAddress,this.mDstPort);
		}else {
		      data = Utils.combineArray(heads,this.mDstAddress,this.mDstPort);  
		}
		return data;
	}


	private InetAddress readServerAddress(int addressType) throws IOException {
		InetAddress address = null;
		if(addressType==Configs.ADDRESS_IPV4) {
			mDstAddress = readClientBytes(4);
			address = Utils.calcInetIpV4Address(mDstAddress);
		}else if(addressType==Configs.ADDRESS_IPV6) {
			mDstAddress = readClientBytes(16);
			address = Utils.calcInetIpV6Address(mDstAddress);
		}if(addressType==Configs.ADDRESS_DOMAIN) {
			byte[] data = readClientBytes(1);
			int len = data[0];
			mDstAddress = readClientBytes(len);
			address = Utils.calcInetHostNameAddress(mDstAddress);
		}
		return address;
	}
	

	private int readServerPort() throws IOException {
		byte[] dstPort = readClientBytes(2);
		this.mDstPort = dstPort;
		return  Utils.calcPort( dstPort[0], dstPort[1] );
	}

	
	private byte[] readClientBytes(int len) throws IOException {
		byte[] buf  = new byte[len];
		mClientInputStream.read(buf, 0, len);
		return buf;
	}

	private byte readClientByte() throws IOException {
		if(mClientInputStream==null) {
			return -1;
		}
		byte[] buf =  readClientBytes(1);
		if(buf==null || buf.length==0) {
			return -1;
		}
		return buf[0];
	}
	

	private boolean checkAuthentication() throws IOException {
	
		if(!Configs.shouldAuthUser()) {
			boolean isFind = findAuthMethods(METHOD_ALLOWALL);
			if(isFind) {
				sendToClient(AUTH_NONE);
				return true;
			}
			LogUtils.E("Socks 5 is not support these auth methods");
			return false;
		}
		
		boolean isFind = findAuthMethods(METHOD_AUTHUSER);
		if(!isFind) {
			LogUtils.E("Socks 5 is not support these auth methods");
		}
		sendToClient(AUTH_USER_PASSWORD);
		
		int rfc_Version = readClientByte();
		if(rfc_Version!=Configs.VERSION_RCF1921) {
			LogUtils.E("Socks 5 Error,Don't Support to switch socks version");
			sendToClient(AUTH_STATE_FAILED);
			return false;
		}
		String userName = readUserName();
		String password = readUserPassword();
		
		boolean isAllowUser = UserManager.getInstance().handleUserInfo(userName,password);
		
		if(!isAllowUser) {
			sendToClient(AUTH_STATE_FAILED);
			return false;
		}
		sendToClient(AUTH_STATE_SUCCESS);
		return true;
		
	}

	private String readUserName() throws IOException {
		byte usenameLength = (byte) mClientInputStream.read();
		byte[] buf = new byte[usenameLength];
		for (int i = 0; i < usenameLength; i++) {
			buf[i] =  (byte) mClientInputStream.read();
		}
		String userName = new String(buf, 0, usenameLength);
		return userName;
	}
	
	private String readUserPassword() throws IOException {
		byte passwordLength = (byte) mClientInputStream.read();
		byte[] buf = new byte[passwordLength];
		for (int i = 0; i < passwordLength; i++) {
			buf[i] =  (byte) mClientInputStream.read();
		}
		String password = new String(buf, 0, passwordLength);
		return password;
	}

	private void sendToClient(byte[] data) throws IOException {
		sendToClient(data, data.length);
	}
	
	private void sendToClient(byte[] data,int len) throws IOException {
		if(data==null) return;
		mClientOutputStream.write(data, 0, len);
	}
	/**
	 * 鏌ヨ鏍￠獙鏂规硶
	 * @param methodName
	 * @return
	 */
	private boolean findAuthMethods(int methodName) {
		for (int i = 0; i < CLIENT_SUPPORT_METHODS.length; i++) {
			if(CLIENT_SUPPORT_METHODS[i]==methodName) {
				return true;
			}
		}
		return false;
	}

	private void readHeader() throws IOException {
		prepareClientIO();
		
		byte methodNum = readClientByte();
		CLIENT_SUPPORT_METHODS = new int[methodNum];
		for (int i = 0; i < methodNum; i++) {
			CLIENT_SUPPORT_METHODS[i] = mClientInputStream.read();
		}
		
	}

	private void prepareClientIO() throws IOException {
		try {
			if(client==null || !client.isConnected()) {
				throw new IOException("the connetion from client is closed");
			}
			if(mClientInputStream==null)
			{
				mClientInputStream = client.getInputStream();
			}
			if(mClientOutputStream==null) {
				mClientOutputStream = client.getOutputStream();
			}
		} catch (IOException e) {
			e.printStackTrace();
			throw e;
		}
	}
	
	
	private void startConnect() throws Exception {
		
		try {
			SocketFactory socketFactory = SocketFactory.getDefault();
			Socket targetServerSocket = socketFactory.createSocket(mServerAddress, mServerPort);
			targetServerSocket.setTcpNoDelay(true);
			targetServerSocket.setSoTimeout(Configs.READ_TIMEOUT);
			
			server = targetServerSocket;
			
			mServerInputStream 		= targetServerSocket.getInputStream();
			mServerOutputStream  	= targetServerSocket.getOutputStream();
			
			acceptAction();
		} catch (Exception e) {
			e.printStackTrace();
			refuseAction(RESPONSE_CODES[0x01]);
			throw new Exception("Socks 4 - Can't connect to " +Utils.getSocketInfo( server ) );
		}
		
	}
	
	private void startBind() throws IOException {
		InetAddress		hostAddress		= null;
		int				hostPort	= 0; //
		
		Map<String, Object> localInetIdent = NetworkUtils.getLocalInetIdent();
		if(localInetIdent==null) {
			refuseAction( RESPONSE_CODES[0x03]);
			return;
		}
		String ipHost  = (String) localInetIdent.get("ip");
		hostAddress = InetAddress.getByName(ipHost);
		
		LogUtils.E("本地主机IP =" +hostAddress);
		
		try	{	
			mBindTempServer = new ServerSocket( 0 );
			mBindTempServer.setSoTimeout( Configs.READ_TIMEOUT );
			hostPort	= mBindTempServer.getLocalPort();
		}
		catch( IOException e )	{ 
			LogUtils.E( "Error in BIND() - Can't BIND at any Port" );
			replyBind( (byte)RESPONSE_CODES[0x05], hostAddress,hostPort );
			throw e;
		}

		LogUtils.E( "BIND at : <"+ipHost+":"+hostPort+">" );
		replyBind( (byte)RESPONSE_CODES[0x00], hostAddress, hostPort ); //鍙戦�佺粰socks client,socks client閫氳繃connect璁╄繙绋嬫湇鍔″櫒鏉ヨ繛鎺ヨ繖涓猻socks
									
		if( clientIsRefusedBind() ) {  //鍝嶅簲鏁版嵁鍚庯紝CONNECT涓殑鎺у埗娴佷笉搴旇鍙戦�佹暟鎹紝鍚﹀垯瑙嗕负鎷掔粷
			LogUtils.E( "BIND - Client connection closed" );
			return;
		}
		
		Socket	socket = null;
		try {
			socket = mBindTempServer.accept();
			socket.setSoTimeout( Configs.READ_TIMEOUT );
		}
		catch( Exception e ) {
			socket.close();
			throw e;
		}
		
		mServerAddress	= socket.getInetAddress();
		mServerPort	    = socket.getPort();
		
		replyBind((byte)RESPONSE_CODES[0x00],	mServerAddress,mServerPort);
		
		server =  socket;
		
		mServerInputStream 		= server.getInputStream();
		mServerOutputStream  	= server.getOutputStream();
		
		mBindTempServer.close();
	}
	
	
	private boolean clientIsRefusedBind() {
		try {
			if(mClientInputStream==null) return true;
			int len = mClientInputStream.read(new byte[128], 0, 128);
			return len>=0;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return false;
	}

	public	void	replyBind( byte respCode, InetAddress hostAddress, int port ) throws IOException
	{
		
		if(port==-1 || hostAddress==null) {
			throw new UnknownHostException("Can't resolve the host!");
		} 

		LogUtils.E( "BIND Reply to Client [" + Utils.getCodeDesc(respCode)+"]" );
		byte[] IP = hostAddress.getAddress();
		byte[] PORT = {(byte)((port & 0xFF00) >> 8),(byte) (port & 0x00FF)};
		
		byte[]	dataBuf = new byte[4];
			
		dataBuf[0] = Configs.VERSION_SOCKS_V5;
		dataBuf[1] = (byte)((int)respCode - 90);	// Reply Code;
		dataBuf[2] = 0x00;		// Reserved	'00'
		dataBuf[3] = (byte) ((Inet6Address.class.isInstance(hostAddress))?Configs.ADDRESS_IPV6:Configs.ADDRESS_IPV4);		// IP ver.4 Type
		
		dataBuf = Utils.combineArray(dataBuf,IP,PORT);//排除domain类型，网卡地址不存在域名

		sendToClient(dataBuf);
		
	} 
	
	private void acceptAction() throws IOException {
			
			byte[] data = getCheckReplyResult(RESPONSE_CODES[0x00]);
			sendToClient(data);
	}

	public boolean isReady() {
		return (server!=null && server.isConnected()&&client!=null&& client.isConnected());
	}
	
	
	public	void startUdp() throws IOException {
		
		//	Connect to the Remote Host
		
		try	{
			mDGSocket  = new DatagramSocket();
			initUdpInOut();
		}
		catch( IOException e )	{
			refuseAction( (byte)RESPONSE_CODES[0x05] ); 
			// Connection Refused
			throw new IOException( "Connection Refused - FAILED TO INITIALIZE UDP Association." );
		}
		
		//TODO: InetAddress	hostAddress   	= client.getLocalAddress();
		Map<String, Object> localInetIdent = NetworkUtils.getLocalInetIdent();
		if(localInetIdent==null) {
			refuseAction( RESPONSE_CODES[0x03]);
			return;
		}
		String ipHost  = (String) localInetIdent.get("ip");
		
		InetAddress	hostAddress   	= InetAddress.getByName(ipHost);
		int			port 			= mDGSocket.getLocalPort();
		
		//	Return response to the Client   
		// Code '00' - Connection Succeeded,
		// IP/Port where Server will listen
		replyUdp( (byte)0, hostAddress, port );
		
		LogUtils.E( "UDP Listen at: <"+hostAddress.toString()+":"+port+">" );

		while(checkClientHasData())
		{
			processUdp();
			Thread.yield();
		}
		LogUtils.E( "UDP - Closed TCP Master of UDP Association" );
	} // UDP ...
	
	private boolean checkClientHasData() {
		
		synchronized( this.UID.intern() )
		{
			int len = 	tryReadFromClient();
			if( len>=0) {
				return true;
			}
			
			shutDown();
		}
		return false;
		
	}

	public int tryReadFromClient() {
		
		//	The client side is not opened.
			if( mClientInputStream == null )	return -1;
	
			int	dlen = 0;
			try
			{
				dlen = mClientInputStream.read( mUdpBuffer, 0, Configs.BUFFER_DEFAULT_SIZE );
			}
			catch( InterruptedIOException e )		{
				return	0;
			}
			catch( IOException e )		{
				LogUtils.E( "Client connection Closed!" );
				//	Close the server on this exception
				return -1;
			}
			return	dlen;
		
	}
	
	/**
	 * 初始化udp
	 * @throws IOException
	 */
	private	void	initUdpInOut()	throws IOException {
		
		mDGSocket.setSoTimeout (Configs.SO_TIMEOUT);	
		mUdpBuffer = new byte[ Configs.BUFFER_DEFAULT_SIZE ];
		
		mDGPack = new DatagramPacket( mUdpBuffer, mUdpBuffer.length);
	}
	
	private	byte[]	addDgpHead( byte[]	buffer )	{
				
		//int		bl			= Buffer.length;
		byte	IABuf[]		= mDGPack.getAddress().getAddress();
		int		DGport		= mDGPack.getPort();
		int		HeaderLen	= 6 + IABuf.length;
		int		DataLen		= mDGPack.getLength();
		int		NewPackLen	= HeaderLen + DataLen;
		
		byte	UB[] = new byte[ NewPackLen ];
		
		UB[0] = (byte)0x00;	// Reserved 0x00
		UB[1] = (byte)0x00;	// Reserved 0x00
		UB[2] = (byte)0x00;	// FRAG '00' - Standalone DataGram
		UB[3] = (byte)0x01;	// Address Type -->'01'-IP v4
		System.arraycopy( IABuf,0, UB,4, IABuf.length );
		UB[4+IABuf.length] = (byte)((DGport >> 8) & 0xFF);
		UB[5+IABuf.length] = (byte)((DGport     ) & 0xFF);
		System.arraycopy( buffer,0, UB, 6+IABuf.length, DataLen );
		System.arraycopy( UB,0, buffer,0, NewPackLen );
		
		return	UB;
		
	} 
	
	private	byte[]	clearDgpHead( byte[] buffer )	{
		//int	IAlen = 0;
		//int	bl	= Buffer.length;
		int	p	= 4;	// First byte of IP Address
		
		byte	addressType = buffer[3];	// IP Address Type
//		switch( addressType )	{
//		case	0x01:	IAlen = 4;
//		break;
//		case	0x03:	
//			IAlen = buffer[p]+1; break; // One for Size Byte
//		default	:	
//			LogUtils.E( "Error in ClearDGPhead() - Invalid Destination IP Addres type " + addressType );
//			return null;
//		}

//		byte	IABuf[] = new byte[IAlen];
//		System.arraycopy( buffer, p, IABuf, 0, IAlen );
//		p += IAlen;
		
		
		if(addressType==Configs.ADDRESS_IPV4) {
			int IAlen = Configs.ADDRESS_IPV4_LENGTH;
			byte	IABuf[] = new byte[IAlen];
			System.arraycopy( buffer, p, IABuf, 0, IAlen );
			p += IAlen;
			UDP_IA = Utils.calcInetIpV4Address(mDstAddress);
		}else if(addressType==Configs.ADDRESS_IPV6) {
			int IAlen = Configs.ADDRESS_IPV6_LENGTH;
			byte	IABuf[] = new byte[IAlen];
			System.arraycopy( buffer, p, IABuf, 0, IAlen );
			p += IAlen;
			
			UDP_IA = Utils.calcInetIpV6Address(mDstAddress);
		}if(addressType==Configs.ADDRESS_DOMAIN) {
			int IAlen = buffer[p];
			p = 5;
			
			byte	IABuf[] = new byte[IAlen];
			System.arraycopy( buffer, p, IABuf, 0, IAlen );
			p += IAlen;
	
			UDP_IA = Utils.calcInetHostNameAddress(IABuf);
		}
		
		
		UDP_port = Utils.calcPort( buffer[p++], buffer[p++] );
		
		if( UDP_IA == null )	{
			LogUtils.E( "Error in ClearDGPHead() - Invalid UDP dest IP address: NULL" );
			return null;
		}
		
		int	DataLen = mDGPack.getLength();
		DataLen -= p; // <p> is length of UDP Header
		
		byte	UB[] = new byte[ DataLen ];
		System.arraycopy( buffer,p, UB,0, DataLen );
		System.arraycopy( UB,0, buffer,0, DataLen );
		
		return UB;
		
	} 

	protected	void	udpSend( DatagramPacket	DGP )	{
	
		if( DGP == null )	return;
		
		String	LogString =	DGP.getAddress()+ ":" + 
							DGP.getPort()	+ "> : " + 
							DGP.getLength()	+ " bytes";
		try	{
			mDGSocket.send( DGP );
		}
		catch( IOException e )	{
			LogUtils.E( "Error in ProcessUDPClient() - Failed to Send DGP to "+ LogString );
			return;
		}
	}
	
	
	public	void	processUdp()	{
		
		// Trying to Receive DataGram
		try	{
        	mDGSocket.receive( mDGPack );
		}
		catch( InterruptedIOException e )	{
			return;	// Time Out		
		}
		catch( IOException e )	{
			LogUtils.E( "Error in ProcessUDP() - "+ e.toString() );
			return;
		}
		
		if( mClientAddress.equals( mDGPack.getAddress() ) )	{

			processUdpClient();
		}
		else	{

			processUdpRemote();
		}
		
		try	{
			initUdpInOut();	// Clean DGPack & Buffer
		}
		catch( IOException e )	{
			LogUtils.E( "IOError in Init_UDP_IO() - "+ e.toString() );
			shutDown();
		}
	} 

	protected void shutDown() {
		
		Utils.closeQuiety(mClientInputStream);
		Utils.closeQuiety(mClientOutputStream);
		
		Utils.closeQuiety(mServerInputStream);
		Utils.closeQuiety(mClientOutputStream);
		
		Utils.closeQuiety(client);
		Utils.closeQuiety(server);
		Utils.closeQuiety(mBindTempServer);
		
		client = null;
		server = null;
		
	}

	/** Processing Client's datagram
	 * This Method must be called only from <ProcessUDP()>
	*/
	public	void	processUdpClient()	{
		
		mUdpClientPort = mDGPack.getPort();

		// Also calculates UDP_IA & UDP_port ...
		byte[]	Buf = clearDgpHead( mDGPack.getData() );
		if( Buf == null )	return;
		
		if( Buf.length <= 0 )	return;				

		if( UDP_IA == null )	{
			LogUtils.E( "Error in ProcessUDPClient() - Invalid Destination IP - NULL" );
			return;
		}
		if( UDP_port == 0 )	{
			LogUtils.E( "Error in ProcessUDPClient() - Invalid Destination Port - 0" );
			return;
		}
		
		if( mServerAddress != UDP_IA || mServerPort != UDP_port )	{
			mServerAddress		= UDP_IA;
			mServerPort	= UDP_port;
		}
		
		LogUtils.I( "Datagram : "+ Buf.length + " bytes : "+Utils.getSocketInfo(	mDGPack )+
					 " >> <" + Utils.iP2Str( mServerAddress )+":"+mServerPort+">" );
		
		DatagramPacket	DGPSend = new DatagramPacket( Buf, Buf.length,
													  UDP_IA, UDP_port );
		
		udpSend( DGPSend );
	}		
	
	
	
	public	void	processUdpRemote()	{

		LogUtils.I( "Datagram : "+ mDGPack.getLength()+" bytes : "+
					 "<"+Utils.iP2Str( mClientAddress )+":"+mClientPort+"> << " +
					 Utils.getSocketInfo( mDGPack ) );
		
		// This Method must be CALL only from <ProcessUDP()>
		// ProcessUDP() Reads a Datagram packet <DGPack>

		InetAddress	DGP_IP	= mDGPack.getAddress();
		int			DGP_Port= mDGPack.getPort();
		
		byte[]	Buf;
	
		Buf = addDgpHead( mUdpBuffer );
		if( Buf == null )	return;
		
		// SendTo Client
		DatagramPacket	DGPSend = new DatagramPacket( Buf, Buf.length,
													  mClientAddress, mClientPort );
		udpSend( DGPSend );
		
		if( DGP_IP != UDP_IA || DGP_Port != UDP_port )	{
			mServerAddress		= DGP_IP;
			mServerPort	= DGP_Port;
		}
	}	
	
	public	void	replyUdp( byte respCode, InetAddress address, int port )
			throws	IOException	{
			
			LogUtils.I( "Reply to Client  [" + Utils.getCodeDesc(respCode)+"]" );
			
		
			byte[]	IP = address.getAddress();
			byte[] PORT = {(byte)((port & 0xFF00) >> 8),(byte) (port & 0x00FF)};
				 
			byte[]	REPLY = new byte[10];
				
			REPLY[0] = Configs.VERSION_SOCKS_V5;
			REPLY[1] = respCode;	// Reply Code;
			REPLY[2] = 0x00;		// Reserved	'00'
			REPLY[3] =  (byte) ((Inet6Address.class.isInstance(address))?Configs.ADDRESS_IPV6:Configs.ADDRESS_IPV4);		// Address Type	IP v4
			
			REPLY = Utils.combineArray(REPLY,IP,PORT);
			
			if( client == null )	{
				LogUtils.E( "Error in UDP_Reply() - Client socket is NULL" );	
				
			}
			sendToClient( REPLY );// BND.PORT
		} 
	
	
	public	void	logServerData( int traffic )	{
		LogUtils.I("Server->Client : "+
					mServerAddress.getHostAddress()+":" +mServerPort
					+" >> <"+
					mClientAddress.getHostAddress() +":"+mClientPort
					+"> : " + 
					traffic +" bytes." );
	}
	

	public	void	logClientData( int traffic )	{
		LogUtils.I("Client->Server: "+
				mClientAddress.getHostAddress() +":"+mClientPort
				+" >> <"+
				mServerAddress.getHostAddress()+":" +mServerPort
				+"> : " 
				+ traffic +" bytes." );
	}
	
}
